/*
  flamingo - 2D Game Engine
  Copyright (C) 2009 Bradley Zeis

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.

  Bradley Zeis
  flamingoengine@gmail.com
*/

#define FLMATH      1
#define IMAGE       1
#define MESH        1
#define FLCOLOR     1
#define SPRITE      1

#include "flamingo.h"
#include "flcolor.h"
#include "flmath.h"
#include "IL/il.h"
#include "image.h"
#include "mesh.h"

#include "doc/sprite_doc.h"
#include "sprite.h"

extern PyTypeObject __declspec(dllimport) Color_Type;
extern PyTypeObject __declspec(dllimport) Vector_Type;
extern PyTypeObject __declspec(dllimport) Polygon_Type;
extern PyTypeObject __declspec(dllimport) Rect_Type;
extern PyTypeObject __declspec(dllimport) Texture_Type;

extern PyTypeObject __declspec(dllimport) Bone_Type;
extern PyTypeObject __declspec(dllimport) Mesh_Type;

PyTypeObject __declspec(dllexport) ObjectState_Type;
PyTypeObject __declspec(dllexport) Sprite_Type;
PyTypeObject __declspec(dllexport) Group_Type;
PyTypeObject __declspec(dllexport) UGroup_Type;
PyTypeObject __declspec(dllexport) MGroup_Type;

PyObject *core_module;

/* -------- UTILITY -------- */
void uchange_tier(Sprite *spr, int new_tier) {
    // Delete from old group
    PyObject *dict = PyModule_GetDict(core_module);
    PyObject *core_data = PyDict_GetItemString(dict, "_Data");
    PyObject *spr_grps = PyObject_GetAttrString(core_data, "spr_groups");
    PyObject *group = GETITEM(spr_grps, spr->tier - 1);
    PyObject *val = Py_BuildValue("(O)", spr);
    PyObject *result = Group_remove((Group *)group, val);
    Py_DECREF(group);
    Py_DECREF(result);

    // Add to/create new group
    PyObject *ngroup;
    while (PySequence_Length(spr_grps) != new_tier) {
        ngroup = PyObject_CallFunction((PyObject *)&UGroup_Type, "");
        PyList_Append(spr_grps, ngroup);
        Py_DECREF(ngroup);
    }

    group = GETITEM(spr_grps, new_tier - 1);
    result = Group_add((Group *)group, val);
    Py_DECREF(val);
    Py_DECREF(group);
    Py_DECREF(result);
    spr->tier = new_tier;

    // Recursively update children's tiers
    Sprite *current;
    int i, l = PySequence_Length(spr->children);
    for (i = 0; i < l; i++) {
        current = (Sprite *)GETITEM(spr->children, i);
        uchange_tier(current, new_tier + 1);
        Py_DECREF(current);
    }
}

int uObjectState_SetColor(ObjectState *self, PyObject *color) {
    Py_DECREF(self->color);
    if (PyObject_TypeCheck(color, &Color_Type)) {
        Py_INCREF(color);
        self->color = (Color *)color;
        return 0;
    } else {
        self->color = (Color *)PyObject_CallFunction((PyObject *)&Color_Type, "O", color);
        if (self->color == NULL) {
            PyErr_Format(PyExc_TypeError, "argument must be Color-like");
            return -1;
        }
        return 0;
    }
    return 0;
}

void uObjectState_interpolate(ObjectState *self, ObjectState *other, ObjectState *o, double i) {
    
    int j;
    double selfx, selfy, otherx, othery;
    for (j = 0; j < self->bound->length; j++) {
        selfx = self->bound->points[j]->pt->x;
        selfy = self->bound->points[j]->pt->y;
        otherx = other->bound->points[j]->pt->x;
        othery = other->bound->points[j]->pt->y;
        
        o->bound->points[j]->pt->x = selfx + (i * (otherx - selfx));
        o->bound->points[j]->pt->y = selfy + (i * (othery - selfy));
    }
    
    double ix, iy, io;
    ix = self->bound->center->pt->x + (i * (other->bound->center->pt->x - self->bound->center->pt->x));
    iy = self->bound->center->pt->y + (i * (other->bound->center->pt->y - self->bound->center->pt->y));
    io = self->bound->orientation + (i * (other->bound->orientation - self->bound->orientation));
    o->bound->center->pt->x = ix;
    o->bound->center->pt->y = iy;
    o->bound->orientation = io;
    PyObject *val = Py_BuildValue("(i)", 0);
    Polygon_setcache((Polygon *)o->bound, val);

    double ir, ig, ib, ia;
    ir = self->color->r + (i * (other->color->r - self->color->r));
    ig = self->color->g + (i * (other->color->g - self->color->g));
    ib = self->color->b + (i * (other->color->b - self->color->b));
    ia = self->color->a + (i * (other->color->a - self->color->a));

    o->color->r = ir;
    o->color->g = ig;
    o->color->b = ib;
    o->color->a = ia;
}

/* Test Test Test */
Mesh *uNewMesh(double width, double height) {
    double hw, hh;
    hw = width / 2.0;
    hh = height / 2.0;

    VMeshNode *v0 = uNewVMeshNode(-hw, -hh);
    MeshNode *t0 = uNewMeshNode(0, 0);
    VMeshNode *v1 = uNewVMeshNode(hw, -hh);
    MeshNode *t1 = uNewMeshNode(1, 0);
    VMeshNode *v2 = uNewVMeshNode(hw, hh);
    MeshNode *t2 = uNewMeshNode(1, 1);
    VMeshNode *v3 = uNewVMeshNode(-hw, hh);
    MeshNode *t3 = uNewMeshNode(0, 1);

    Mesh *mesh = (Mesh *)PyObject_CallFunction((PyObject *)&Mesh_Type, "[OOOO][OOOO]",
                                               v0, v1, v2, v3, t0, t1, t2, t3);

    Py_DECREF(v0);
    Py_DECREF(t0);
    Py_DECREF(v1);
    Py_DECREF(t1);
    Py_DECREF(v2);
    Py_DECREF(t2);
    Py_DECREF(v3);
    Py_DECREF(t3);

    return mesh;
}

Polygon *uNewPolygon1(double width, double height) {
    double hw, hh;
    hw = width / 2.0;
    hh = height / 2.0;
    
    Vector *v0 = uNewVector(-hw, -hh);
    Vector *v1 = uNewVector(0, -hh);
    Vector *v2 = uNewVector(hw, -hh);
    Vector *v3 = uNewVector(hw, 0);
    Vector *v4 = uNewVector(hw, hh);
    Vector *v5 = uNewVector(0, hh);
    Vector *v6 = uNewVector(-hw, hh);
    Vector *v7 = uNewVector(-hw, 0);

    Polygon *p = (Polygon *)PyObject_CallFunction((PyObject *)&Polygon_Type, "((OOOOOOOO))",
                                                   v0, v1, v2, v3, v4, v5, v6, v7);
    
    Py_DECREF(v0);
    Py_DECREF(v1);
    Py_DECREF(v2);
    Py_DECREF(v3);
    Py_DECREF(v4);
    Py_DECREF(v5);
    Py_DECREF(v6);
    Py_DECREF(v7);

    return p;
}

Mesh *uNewMesh1(double width, double height) {
    double hw, hh;
    hw = width / 2.0;
    hh = height / 2.0;

    VMeshNode *v0 = uNewVMeshNode(-hw, -hh);
    MeshNode *t0 = uNewMeshNode(0, 0);
    VMeshNode *v1 = uNewVMeshNode(0, -hh / 1.5);
    MeshNode *t1 = uNewMeshNode(0.5, 0);
    VMeshNode *v2 = uNewVMeshNode(hw, -hh);
    MeshNode *t2 = uNewMeshNode(1, 0);
    VMeshNode *v3 = uNewVMeshNode(hw / 1.5, 0);
    MeshNode *t3 = uNewMeshNode(1, 0.5);
    VMeshNode *v4 = uNewVMeshNode(hw, hh);
    MeshNode *t4 = uNewMeshNode(1, 1);
    VMeshNode *v5 = uNewVMeshNode(0, hh / 1.5);
    MeshNode *t5 = uNewMeshNode(0.5, 1);
    VMeshNode *v6 = uNewVMeshNode(-hw, hh);
    MeshNode *t6 = uNewMeshNode(0, 1);
    VMeshNode *v7 = uNewVMeshNode(-hw / 1.5, 0);
    MeshNode *t7 = uNewMeshNode(0, 0.5);

    Mesh *mesh = (Mesh *)PyObject_CallFunction((PyObject *)&Mesh_Type, "[OOOOOOOO][OOOOOOOO]",
                                               v0, v1, v2, v3, v4, v5, v6, v7,
                                               t0, t1, t2, t3, t4, t5, t6, t7);

    Py_DECREF(v0);
    Py_DECREF(t0);
    Py_DECREF(v1);
    Py_DECREF(t1);
    Py_DECREF(v2);
    Py_DECREF(t2);
    Py_DECREF(v3);
    Py_DECREF(t3);
    Py_DECREF(v4);
    Py_DECREF(t4);
    Py_DECREF(v5);
    Py_DECREF(t5);
    Py_DECREF(v6);
    Py_DECREF(t6);
    Py_DECREF(v7);
    Py_DECREF(t7);

    return mesh;
}

Bone *uNewBone1(double width, double height) {
    double hw, hh;
    hw = width / 2.0;
    hh = height / 2.0;
    
    Vector *vr = uNewVector(0, 0);
    Vector *v00 = uNewVector(-hw / 6, -hh / 2);
    Vector *v01 = uNewVector(hw / 2, -hh / 2);
    Vector *v10 = uNewVector(hw / 2, -hh / 6);
    Vector *v11 = uNewVector(hw / 2, -hh / 2);
    Vector *v20 = uNewVector(hw / 6, hh / 2);
    Vector *v21 = uNewVector(hw / 2, -hh / 2);
    Vector *v30 = uNewVector(-hw / 2, hh / 6);
    Vector *v31 = uNewVector(hw / 2, -hh / 2);
    
    Py_INCREF(Py_None);
    Bone *r = (Bone *)PyObject_CallFunction((PyObject *)&Bone_Type, "OO", Py_None, vr);
    Bone *b00 = (Bone *)PyObject_CallFunction((PyObject *)&Bone_Type, "OO", r, v00);
    Bone *b01 = (Bone *)PyObject_CallFunction((PyObject *)&Bone_Type, "OO", b00, v01);
    Bone *b10 = (Bone *)PyObject_CallFunction((PyObject *)&Bone_Type, "OO", r, v10);
    Bone *b11 = (Bone *)PyObject_CallFunction((PyObject *)&Bone_Type, "OO", b10, v11);
    Bone *b20 = (Bone *)PyObject_CallFunction((PyObject *)&Bone_Type, "OO", r, v20);
    Bone *b21 = (Bone *)PyObject_CallFunction((PyObject *)&Bone_Type, "OO", b20, v21);
    Bone *b30 = (Bone *)PyObject_CallFunction((PyObject *)&Bone_Type, "OO", r, v30);
    Bone *b31 = (Bone *)PyObject_CallFunction((PyObject *)&Bone_Type, "OO", b30, v31);
    
    Py_DECREF(v00);
    Py_DECREF(v01);
    Py_DECREF(v10);
    Py_DECREF(v11);
    Py_DECREF(v20);
    Py_DECREF(v21);
    Py_DECREF(v30);
    Py_DECREF(v31);
    Py_DECREF(b00);
    Py_DECREF(b01);
    Py_DECREF(b10);
    Py_DECREF(b11);
    Py_DECREF(b20);
    Py_DECREF(b21);
    Py_DECREF(b30);
    Py_DECREF(b31);
    
    return r;
}

/* -------- OBJECTSTATE METHODS -------- */
/* Initialization */
void ObjectState_dealloc(ObjectState *self) {
    Py_XDECREF(self->bound);
    self->ob_type->tp_free((PyObject *)self);
}

PyObject *ObjectState_new(PyTypeObject *type, PyObject *args, PyObject *kwds) {
    ObjectState *self;
    self = (ObjectState *)type->tp_alloc(type, 0);

    if (self != NULL) {
        self->color = uNewColor(1, 1, 1, 1);
    }

    return (PyObject *)self;
}

int ObjectState_init(ObjectState *self, PyObject *args) {
    PyObject *r, *c=NULL;
    if (!PyArg_ParseTuple(args, "O|O", &r, &c))
        return -1;

    // Bound
    if (!PyObject_TypeCheck(r, &Polygon_Type)) {
        PyErr_Format(PyExc_TypeError, "argument must be 'flamingo.flmath.Polygon', not type '%s'",
                     r->ob_type->tp_name);
        return -1;
    }
    self->bound = (Polygon *)Polygon_copy((Polygon *)r);

    // Color
    if (c == NULL)
        return 0;
    
    return uObjectState_SetColor(self, c);
}

PyObject *ObjectState_reduce(ObjectState *self) {
    return Py_BuildValue("(O(OO))", self->ob_type, self->bound, self->color);
}

/* Presentation */
PyObject *ObjectState_repr(ObjectState *self) {
    char string[64];
    sprintf(string, "<sprite.ObjectState(%i)>", (int)self);
    return PyString_FromString(string);
}

PyObject *ObjectState_str(ObjectState *self) {
    return ObjectState_repr(self);
}

/* General Functions */
PyObject *ObjectState_copy(ObjectState *self) {
    Polygon *p = (Polygon *)Polygon_copy(self->bound);
    PyObject_SetAttrString((PyObject *)p, "orientation", PyFloat_FromDouble(self->bound->orientation));
    return PyObject_CallFunction((PyObject *)&ObjectState_Type, "OO",
                                 (PyObject *)p, uNewColor(self->color->r, self->color->g,
                                                          self->color->b, self->color->a));
}

void ObjectState_merge(ObjectState *self, ObjectState *other) {
    int i;
    for (i = 0; i < self->bound->length; i++) {
        self->bound->points[i]->pt->x = other->bound->points[i]->pt->x;
        self->bound->points[i]->pt->x = other->bound->points[i]->pt->x;
    }
    
    self->bound->center->pt->x = other->bound->center->pt->x;
    self->bound->center->pt->y = other->bound->center->pt->y;
    self->bound->orientation = other->bound->orientation;
    PyObject *val = Py_BuildValue("(i)", 0);
    PyObject *result = Polygon_setcache((Polygon *)other->bound, val);
    Py_DECREF(val);
    Py_DECREF(result);

    self->color->r = other->color->r;
    self->color->g = other->color->g;
    self->color->b = other->color->b;
    self->color->a = other->color->a;
}

PyObject *ObjectState_interpolate(ObjectState *self, PyObject *args) {
    ObjectState *other;
    double interpolation;
    if (!PyArg_ParseTuple(args, "Od", &other, &interpolation))
        return NULL;

    if (!PyObject_TypeCheck(other, &ObjectState_Type)) {
        return NULL;
    }

    ObjectState *o = (ObjectState *)ObjectState_copy(self);
    uObjectState_interpolate(self, other, o, interpolation);
    return (PyObject *)o;
}

/* Method Declaration */
PyMemberDef ObjectState_members[] = {
    {"bound", T_OBJECT, offsetof(ObjectState, bound), 0, NULL},
    {"color", T_OBJECT, offsetof(ObjectState, color), 0, NULL},
    {NULL}
};

PyMethodDef ObjectState_methods[] = {
    {"copy", (PyCFunction)ObjectState_copy, METH_NOARGS, NULL},
    {"interpolate", (PyCFunction)ObjectState_interpolate, METH_VARARGS, NULL},
    {"__reduce__", (PyCFunction)ObjectState_reduce, METH_NOARGS, NULL},
    {NULL}
};

/* -------- SPRITE METHODS -------- */
/* Initialization */
void Sprite_dealloc(Sprite *self) {
    Py_XDECREF(self->groups);
    Py_XDECREF(self->parent);
    Py_XDECREF(self->children);
    Py_XDECREF(self->state);
    Py_XDECREF(self->interstate);
    Py_XDECREF(self->prevstate);
    Py_XDECREF(self->texture);
    Py_XDECREF(self->skins);
    Py_XDECREF(self->bones);
    Py_XDECREF(self->mesh);
    self->ob_type->tp_free((PyObject *)self);
}

PyObject *Sprite_new(PyTypeObject *type, PyObject *args, PyObject *kwds) {
    Sprite *self;
    self = (Sprite *)type->tp_alloc(type, 0);

    if (self != NULL) {
        self->groups = PyDict_New();
        if (self->groups == NULL) {
            Py_DECREF(self);
            return NULL;
        }

        Py_INCREF(Py_None);
        self->parent = Py_None;

        self->children = PyList_New(0);
        if (self->children == NULL) {
            Py_DECREF(self);
            return NULL;
        }

        self->skins = PyDict_New();
        if (self->skins == NULL) {
            Py_DECREF(self);
            return NULL;
        }
        
        self->bones = NULL;
        self->mesh = NULL;
        self->tier = 1;
        self->z = 0;
    }

    return (PyObject *)self;
}

int Sprite_init(Sprite *self, PyObject *args, PyObject *kwds) {
    PyObject *tex_or_skin=NULL, *parent=NULL, *default_skin=NULL;
    char *kwlist[] = {"tex_or_skin", "parent", "default", NULL};

    if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|OO", kwlist,
                                     &tex_or_skin, &parent, &default_skin)) {
        return -1;
    }

    // Create new reference to default_skin
    if (default_skin == NULL) {
        default_skin = PyString_FromString("default");
    } else {
        Py_INCREF(default_skin);
    }

    // Check whether Image or skins is passed
    int T_OR_S;

    if (PyDict_Check(tex_or_skin)) {
        T_OR_S = 1;
    } else if (PyObject_TypeCheck(tex_or_skin, &Texture_Type)) {
        T_OR_S = 0;
    } else {
        PyErr_Format(PyExc_TypeError, "argument must be 'flamingo.image.Texture', not type '%s'",
                     tex_or_skin->ob_type->tp_name);
        return -1;
    }

    // Create Skins, Set default Image
    /* Skins Passed - update the self's skin dict */
    if (T_OR_S == 1) {
        PyDict_Update(self->skins, tex_or_skin);
    }
    /* Image Passed - set as default skin */
    else {
        PyDict_SetItem(self->skins, default_skin, tex_or_skin);
    }

    /* Load default skin */
    self->texture = (Texture *)PyDict_GetItem(self->skins, default_skin);
    if (self->texture == NULL) {
        PyErr_Format(PyExc_AttributeError, "skin '%s' not found", PyString_AsString(default_skin));
        return -1;
    }
    Py_INCREF(self->texture);
    Py_DECREF(default_skin);

    // Create ObjectState (load some custom poly/mesh data eventually)
    //Polygon *p = (Polygon *)uNewRect(0, 0, self->texture->width, self->texture->height);
    Polygon *p = uNewPolygon1(self->texture->width, self->texture->height);
    self->state = (ObjectState *)PyObject_CallFunction((PyObject *)&ObjectState_Type, "O", p);
    Py_DECREF(p);
    self->prevstate = (ObjectState *)ObjectState_copy(self->state);
    self->interstate = (ObjectState *)ObjectState_copy(self->state);

    // Create/Load Mesh
    self->mesh = uNewMesh1(self->texture->width, self->texture->height);
    int i;
    for (i = 0; i < self->state->bound->length; i++) {
        // Eventually only the outer vertices will be used in the Poly. This will
        // need to change then.
        PyMem_Del(self->state->bound->points[i]->pt);
        self->state->bound->points[i]->pt = self->mesh->vnodes[i]->pt;
    }
    
    self->bones = uNewBone1(self->texture->width, self->texture->height);

    // Set Parent/Children/Tier/Group
    if (parent != NULL && parent != Py_None) {

        if (!PyObject_TypeCheck(parent, &Sprite_Type)) {
            PyErr_Format(PyExc_TypeError, "parent must be 'flamingo.sprite.Sprite', not '%s'",
                         parent->ob_type->tp_name);
            return -1;
        }

        self->tier = ((Sprite *)parent)->tier + 1;
        PyList_Append(((Sprite *)parent)->children, (PyObject *)self);

        Py_DECREF(Py_None);
        self->parent = parent;
        Py_INCREF(self->parent);
    }

    PyObject *cdict = PyModule_GetDict(core_module);
    PyObject *core_data = PyDict_GetItemString(cdict, "_Data");
    PyObject *spr_grps = PyObject_GetAttrString(core_data, "spr_groups");
    PyObject *mst_grp = PyObject_GetAttrString(core_data, "master_spr_group");

    /* If there is not a group for self's tier, create group */
    while (PySequence_Length(spr_grps) != self->tier) {
        PyObject *ngroup = PyObject_CallFunction((PyObject *)&UGroup_Type, "");
        PyList_Append(spr_grps, ngroup);
        Py_DECREF(ngroup);
    }

    /* Add self to master group and tier group */
    PyObject *group = GETITEM(spr_grps, self->tier - 1);
    PyObject *val = Py_BuildValue("(O)", (PyObject *)self);
    PyObject *result = Group_add((Group *)group, val);
    Py_DECREF(group);
    Py_DECREF(result);
    Py_DECREF(spr_grps);

    if (mst_grp != Py_None) {
        MGroup_add((MGroup *)mst_grp, val);
    }
    Py_DECREF(val);
    Py_DECREF(mst_grp);

    return 0;
}

PyObject *Sprite_hash(Sprite *self) {
    return PyInt_FromLong(PyObject_Hash(PyInt_FromLong((int)self)));
}

/* Presentation */
PyObject *Sprite_repr(Sprite *self) {
    char string[64];
    sprintf(string, "<sprite.Sprite(%i)>", (int)self);
    return PyString_FromString(string);
}

PyObject *Sprite_str(Sprite *self) {
    return Sprite_repr(self);
}

/* Mapping Operations */
Py_ssize_t Sprite_length(Sprite *self) {
    return PyMapping_Length(self->skins);
}

PyObject *Sprite_subscript(Sprite *self, PyObject *key) {
    PyObject *val = PyDict_GetItem(self->skins, key);
    if (val == NULL) {
        PyObject *str = PyObject_Str(key);
        PyErr_Format(PyExc_KeyError, "invalid subscript '%s'", PyString_AsString(str));
        Py_DECREF(str);
        return NULL;
    }
    Py_INCREF(val);
    return val;
}

/* General Functions */
PyObject *Sprite_public_update(Sprite *self, PyObject *args) {
    Py_RETURN_NONE;
}

PyObject *Sprite_update(Sprite *self, PyObject *args) {
    int dt;
    if (!PyArg_ParseTuple(args, "i", &dt))
        return NULL;
    
    PyObject *result;
    
    // Update Mesh data
    PyObject *val = Py_BuildValue("(i)", 0);
    result = Polygon_setcache(self->state->bound, val);
    Py_DECREF(val);
    Py_DECREF(result);
    
    ObjectState_merge(self->prevstate, self->state);
    result = Sprite_public_update(self, args);
    Py_DECREF(result);
    
    result = PyObject_CallMethod((PyObject *)self->texture, "update", "i", dt);
    Py_DECREF(result);

    Py_RETURN_NONE;
}

PyObject *Sprite_change_skin(Sprite *self, PyObject *args) {
    PyObject *key;
    if (!PyArg_ParseTuple(args, "O", &key))
        return NULL;

    if (PyDict_Contains(self->skins, key) == 0) {
        PyObject *str = PyObject_Str(key);
        PyErr_Format(PyExc_KeyError, "invalid subscript '%s'", PyString_AsString(str));
        Py_DECREF(str);
        return NULL;
    }

    PyObject *changed = PyDict_GetItem(self->skins, key);
    Py_DECREF(self->texture);
    self->texture = (Texture *)changed;
    Py_INCREF(self->texture);

    Py_RETURN_NONE;
}

PyObject *Sprite_add_skin(Sprite *self, PyObject *args) {

    PyObject *key, *value;
    if (!PyArg_ParseTuple(args, "OO", &key, &value))
        return NULL;

    if (!PyObject_TypeCheck(value, &Texture_Type)) {
        PyErr_Format(PyExc_TypeError, "argument must be 'flamingo.image.Texture', not '%s'",
                     value->ob_type->tp_name);
        return NULL;
    }

    if (PyDict_SetItem(self->skins, key, value) != 0) {
        return NULL;
    }
    Py_RETURN_NONE;
}

PyObject *Sprite_remove_skin(Sprite *self, PyObject *args) {
    PyObject *key;
    if (!PyArg_ParseTuple(args, "O", &key))
        return NULL;

    if (PyDict_Contains(self->skins, key) == 0) {
        PyObject *str = PyObject_Str(key);
        PyErr_Format(PyExc_KeyError, "invalid subscript '%s'", PyString_AsString(str));
        Py_DECREF(str);
        return NULL;
    }

    PyObject *removed = PyDict_GetItem(self->skins, key);
    if (self->texture == (Texture *)removed) {
        PyErr_Format(PyExc_AttributeError, "can't remove active skin");
        return NULL;
    }

    if (PyDict_DelItem(self->skins, key) != 0) {
        return NULL;
    }

    Py_RETURN_NONE;
}

PyObject *Sprite_merge_skins(Sprite *self, PyObject *args) {
    PyObject *skins;
    if (!PyArg_ParseTuple(args, "O", &skins))
        return NULL;

    if (!PyDict_Check(skins)) {
        PyObject *str = PyObject_Str(skins);
        PyErr_Format(PyExc_TypeError, "argument must be 'dict', not '%s'",
                     skins->ob_type->tp_name);
        Py_DECREF(str);
        return NULL;
    }

    PyDict_Update(self->skins, skins);
    Py_RETURN_NONE;
}

PyObject *Sprite_add(Sprite *self, PyObject *args) {
    PyObject *group;
    if (!PyArg_ParseTuple(args, "O", &group))
        return NULL;

    if (!PyObject_TypeCheck(group, &Group_Type)) {
        PyErr_Format(PyExc_TypeError, "argument must be 'flamingo.sprite.Group', not '%s'",
                     group->ob_type->tp_name);
        return NULL;
    }

    if (!PyDict_Contains(self->groups, group)) {
        PyObject *val, *result;
        val = Py_BuildValue("(O)", self);
        if (PyObject_TypeCheck(group, &MGroup_Type)) {
            result = MGroup_add((MGroup *)group, val);
        } else {
            result = Group_add((Group *)group, val);
        }
        Py_DECREF(result);
        Py_DECREF(val);
    }

    Py_RETURN_NONE;
}

PyObject *Sprite_remove(Sprite *self, PyObject *args) {
    PyObject *group;
    if (!PyArg_ParseTuple(args, "O", &group))
        return NULL;

    if (!PyObject_TypeCheck(group, &Group_Type)) {
        PyErr_Format(PyExc_TypeError, "argument must be 'flamingo.sprite.Group', not '%s'",
                     group->ob_type->tp_name);
        return NULL;
    }

    if (PyDict_Contains(self->groups, group)) {
        PyObject *val, *result;
        val = Py_BuildValue("(O)", self);
        if (PyObject_TypeCheck(group, &MGroup_Type)) {
            result = MGroup_remove((MGroup *)group, val);
        } else {
            result = Group_remove((Group *)group, val);
        }
        Py_DECREF(result);
        Py_DECREF(val);
    }
    Py_RETURN_NONE;
}

PyObject *Sprite_kill(Sprite *self) {
    PyObject *items = PyDict_Keys(self->groups), *current;
    int i, l = PySequence_Length(items);

    for (i = 0; i < l; i++) {
        current = GETITEM(items, i);
        PyDict_DelItem(((Group *)current)->sprites, (PyObject *)self);

        if (PyObject_TypeCheck(current, &MGroup_Type)) {
            PySequence_DelItem(((MGroup *)current)->sorted, PySequence_Index(((MGroup *)current)->sorted, (PyObject *)self));
        }

        Py_DECREF(current);
    }

    Py_DECREF(items);
    PyDict_Clear(self->groups);

    Py_RETURN_NONE;
}

PyObject *Sprite_groups(Sprite *self, PyObject *args) {
    return PyDict_Keys(self->groups);
}

PyObject *Sprite_alive(Sprite *self, PyObject *args) {
    return PyBool_FromLong(PyMapping_Length(self->groups));
}

PyObject *Sprite_interpolate(Sprite *self, PyObject *args) {
    double interpolation;
    if (!PyArg_ParseTuple(args, "d", &interpolation))
        return NULL;

    return PyObject_CallMethod((PyObject *)self->prevstate, "interpolate", "Od", self->state, interpolation);
}

void Sprite_interpolate_internal(Sprite *self, double interpolation) {
    // Called in screen.c Factor this out (use uObjectState_interpolate)
    uObjectState_interpolate(self->state, self->prevstate, self->interstate, interpolation);
}

/* Properties */
/* Center */
PyObject *Sprite_getcenter(Sprite *self, void *closure) {
    return Polygon_getcenter((Polygon *)self->state->bound, closure);
}

int Sprite_setcenter(Sprite *self, PyObject *value, void *closure) {
    return Polygon_setcenter((Polygon *)self->state->bound, value, closure);
}

PyObject *Sprite_getcenterx(Sprite *self, void *closure) {
    return Polygon_getcenterx((Polygon *)self->state->bound, closure);
}

int Sprite_setcenterx(Sprite *self, PyObject *value, void *closure) {
    return Polygon_setcenterx((Polygon *)self->state->bound, value, closure);
}

PyObject *Sprite_getcentery(Sprite *self, void *closure) {
    return Polygon_getcentery((Polygon *)self->state->bound, closure);
}

int Sprite_setcentery(Sprite *self, PyObject *value, void *closure) {
    return Polygon_setcentery((Polygon *)self->state->bound, value, closure);
}

/* Angle/Orientation */
PyObject *Sprite_getangle(Sprite *self, void *closure) {
    return Polygon_getangle((Polygon *)self->state->bound, closure);
}

int Sprite_setangle(Sprite *self, PyObject *value, void *closure) {
    return Polygon_setangle((Polygon *)self->state->bound, value, closure);
}

PyObject *Sprite_getorientation(Sprite *self, void *closure) {
    return Polygon_getorientation((Polygon *)self->state->bound, closure);
}

int Sprite_setorientation(Sprite *self, PyObject *value, void *closure) {
    return Polygon_setorientation((Polygon *)self->state->bound, value, closure);
}

/* State */
PyObject *Sprite_getstate(Sprite *self, void *closure) {
    Py_INCREF(self->state);
    return (PyObject *)self->state;
}

PyObject *Sprite_getprevstate(Sprite *self, void *closure) {
    Py_INCREF(self->prevstate);
    return (PyObject *)self->prevstate;
}

/* Color */
PyObject *Sprite_getcolor(Sprite *self, void *closure) {
    Py_INCREF(self->state->color);
    return (PyObject *)self->state->color;
}

int Sprite_setcolor(Sprite *self, PyObject *value, void *closure) {
    return uObjectState_SetColor(self->state, value);
}

/* Parent */
PyObject *Sprite_getparent(Sprite *self, void *closure) {
    Py_INCREF(self->parent);
    return (PyObject *)self->parent;
}

int Sprite_setparent(Sprite *self, PyObject *value, void *closure) {
    if (value == self->parent)
        return 0;

    // Remove from previous parent's child list
    if (self->parent != Py_None) {
        Py_ssize_t i = PySequence_Index(((Sprite *)self->parent)->children, (PyObject *)self);
        if (i == -1)
            return -1;
        PySequence_DelItem(((Sprite *)self->parent)->children, i);
    }

    // Set new parent, add self to parent's child list
    if (value == Py_None) {
        Py_DECREF(self->parent);
        self->parent = Py_None;
        Py_INCREF(Py_None);
        if (self->tier != 1)
            uchange_tier(self, 1);
        return 0;
    } else if (PyObject_TypeCheck(value, &Sprite_Type)) {
        Py_DECREF(self->parent);
        self->parent = value;
        Py_INCREF(Py_None);
    } else {
        return -1;
    }

    // Different tier, need to change groups and update children
    if (((Sprite *)self->parent)->tier + 1 != self->tier) {
        uchange_tier(self, ((Sprite *)self->parent)->tier + 1);
    }

    return 0;
}

/* Skins/Texture */
PyObject *Sprite_getskins(Sprite *self, void *closure) {
    Py_INCREF(self->skins);
    return self->skins;
}

PyObject *Sprite_gettexture(Sprite *self, void *closure) {
    Py_INCREF(self->texture);
    return (PyObject *)self->texture;
}

/* Method Declaration */
PyMemberDef Sprite_members[] = {
    {"z", T_INT, offsetof(Sprite, z), 0, NULL},
    {"bones", T_OBJECT_EX, offsetof(Sprite, bones), 0, NULL},
    {"mesh", T_OBJECT_EX, offsetof(Sprite, mesh), 0, NULL},
    {NULL}
};

PyMappingMethods Sprite_MappingMethods = {
    (lenfunc)Sprite_length,                     /* sq_length */
    (binaryfunc)Sprite_subscript,               /* sq_item */
    0,                                          /* sq_ass_item */
};

PyMethodDef Sprite_methods[] = {
    {"change_skin", (PyCFunction)Sprite_change_skin, METH_VARARGS, NULL},
    {"add_skin", (PyCFunction)Sprite_add_skin, METH_VARARGS, NULL},
    {"remove_skin", (PyCFunction)Sprite_remove_skin, METH_VARARGS, NULL},
    {"merge_skins", (PyCFunction)Sprite_merge_skins, METH_VARARGS, NULL},
    {"add", (PyCFunction)Sprite_add, METH_VARARGS, NULL},
    {"remove", (PyCFunction)Sprite_remove, METH_VARARGS, NULL},
    {"kill", (PyCFunction)Sprite_kill, METH_NOARGS, NULL},
    {"groups", (PyCFunction)Sprite_groups, METH_NOARGS, NULL},
    {"alive", (PyCFunction)Sprite_alive, METH_NOARGS, NULL},
    {"update", (PyCFunction)Sprite_public_update, METH_VARARGS, NULL},
    {"interpolate", (PyCFunction)Sprite_interpolate, METH_VARARGS, NULL},
    {"_update", (PyCFunction)Sprite_update, METH_VARARGS, NULL},
    {"__hash__", (PyCFunction)Sprite_hash, METH_NOARGS, NULL},
    {NULL}
};

PyGetSetDef Sprite_getsets[] = {
    {"center", (getter)Sprite_getcenter, (setter)Sprite_setcenter, NULL, NULL},
    {"centerx", (getter)Sprite_getcenterx, (setter)Sprite_setcenterx, NULL, NULL},
    {"centery", (getter)Sprite_getcentery, (setter)Sprite_setcentery, NULL, NULL},
    {"angle", (getter)Sprite_getangle, (setter)Sprite_setangle, NULL, NULL},
    {"orientation", (getter)Sprite_getorientation, (setter)Sprite_setorientation, NULL, NULL},

    {"state", (getter)Sprite_getstate, NULL, NULL, NULL},
    {"prevstate", (getter)Sprite_getprevstate, NULL, NULL, NULL},
    {"color", (getter)Sprite_getcolor, (setter)Sprite_setcolor, NULL, NULL},
    {"parent", (getter)Sprite_getparent, (setter)Sprite_setparent, NULL, NULL},
    {"skins", (getter)Sprite_getskins, NULL, NULL, NULL},
    {"texture", (getter)Sprite_gettexture, NULL, NULL, NULL},
    {NULL}
};

/* -------- _GROUP METHODS -------- */
/* Initialization */
void Group_dealloc(Group *self) {
    Py_XDECREF(self->sprites);
    self->ob_type->tp_free((PyObject *)self);
}

void MGroup_dealloc(MGroup *self) {
    Py_XDECREF(self->sprites);
    Py_XDECREF(self->sorted);
    Py_XDECREF(self->active);
    self->ob_type->tp_free((PyObject *)self);
}

PyObject *Group_new(PyTypeObject *type, PyObject *args, PyObject *kwds) {
    Group *self;
    self = (Group *)type->tp_alloc(type, 0);

    if (self != NULL) {
        self->sprites = PyDict_New();
        if (self->sprites == NULL) {
            Py_DECREF(self);
            return NULL;
        }
    }

    return (PyObject *)self;
}

PyObject *MGroup_new(PyTypeObject *type, PyObject *args, PyObject *kwds) {
    MGroup *self;
    self = (MGroup *)type->tp_alloc(type, 0);

    if (self != NULL) {
        self->sprites = PyDict_New();
        if (self->sprites == NULL) {
            Py_DECREF(self);
            return NULL;
        }

        PyObject *l = PyList_New(0);
        self->sorted = PySequence_Fast(l, "");
        Py_DECREF(l);
        if (self->sorted == NULL) {
            Py_DECREF(self);
            return NULL;
        }
    }

    return (PyObject *)self;
}

int Group_init(Group *self, PyObject *args) {
    return 0;
}

int MGroup_init(Group *self, PyObject *args) {
    return 0;
}

/* Presentation */
PyObject *Group_repr(Group *self) {
    char string[64];
    sprintf(string, "<sprite.Group(%i)>", (int)self);
    return PyString_FromString(string);
}

PyObject *UGroup_repr(Group *self) {
    char string[64];
    sprintf(string, "<sprite._UpdateGroup(%i)>", (int)self);
    return PyString_FromString(string);
}

PyObject *MGroup_repr(Group *self) {
    char string[64];
    sprintf(string, "<sprite._MasterGroup(%i)>", (int)self);
    return PyString_FromString(string);
}

PyObject *Group_str(Group *self) {
    return Group_repr(self);
}

PyObject *UGroup_str(Group *self) {
    return UGroup_repr(self);
}

PyObject *MGroup_str(Group *self) {
    return MGroup_repr(self);
}

/* General Functions */
PyObject *Group_update(Group *self, PyObject *args) {
    Py_RETURN_NONE;
}

PyObject *UGroup_update(Group *self, PyObject *args) {

    PyObject *sprites = PyDict_Keys(self->sprites), *result;
    Sprite *current;
    int i, l = PySequence_Length(sprites);
    for (i = 0; i < l; i++) {
        current = (Sprite *)GETITEM(sprites, i);
        result = Sprite_update(current, args);
        Py_DECREF(result);
        Py_DECREF(current);
    }

    Py_RETURN_NONE;
}

void swap(PyObject *list, int x, int y) {
    PyObject *tempx = PySequence_Fast_GET_ITEM(list, x);
    PyObject *tempy = PySequence_Fast_GET_ITEM(list, y);
    PySequence_SetItem(list, x, tempy);
    PySequence_SetItem(list, y, tempx);
}

void MGroup_sort(MGroup *self) {
    // Insertion sort
    int l = PySequence_Length(self->sorted), j, k;
    Sprite *c1, *c2;

    for (j = 2; j < l; j++) {
        for (k = 0; k < j; k++) {
            c1 = (Sprite *)PySequence_Fast_GET_ITEM(self->sorted, j);
            c2 = (Sprite *)PySequence_Fast_GET_ITEM(self->sorted, k);

            if (c1->z < c2->z)
                swap(self->sorted, j, k);
        }
    }
}

PyObject *MGroup_update(MGroup *self, PyObject *args) {
    // Sort self->sorted
    MGroup_sort(self);

    // Cull Sprites from self->sorted, place those that pass in self->active
    // Slow, this will be reimplemented later
    /* Create active list */
    //Py_XDECREF(self->active);
    //PyObject *l = PyList_New(0);
    //self->active = PySequence_Fast(l, "");
    //Py_DECREF(l);
    //
    ///* Set up loop */
    //PyObject *cdict = PyModule_GetDict(core_module);
    //PyObject *data_class = PyDict_GetItemString(cdict, "_Data");
    //PyObject *screens = PyObject_GetAttrString(data_class, "screens");
    //
    //int i, j, l1 = PySequence_Length(screens), l2 = PySequence_Length(self->sorted);
    //PyObject *current_scr, *val, *result;
    //Sprite *current_spr;
    //
    ///* Cull Sprites */
    //for (i = 0; i < l1; i++) {
    //
    //    current_scr = GETITEM(screens, i);
    //
    //    for (j = 0; j < l2; j++) {
    //        current_spr = (Sprite *)GETITEM(self->sorted, j);
    //
    //        val = Py_BuildValue("(O)", current_spr->state->rect);
    //        result = Polygon_intersects((Polygon *)current_scr, val);
    //        Py_DECREF(val);
    //
    //        if (result == Py_True)
    //            PyList_Append(self->active, (PyObject *)current_spr);
    //        Py_DECREF(result);
    //
    //        Py_DECREF(current_spr);
    //    }
    //
    //    Py_DECREF(current_scr);
    //}
    //
    //Py_DECREF(screens);

    Py_RETURN_NONE;
}

PyObject *Group_add(Group *self, PyObject *args) {
    PyObject *spr;
    if (!PyArg_ParseTuple(args, "O", &spr))
        return NULL;

    if (!PyObject_TypeCheck(spr, &Sprite_Type)) {
        PyErr_Format(PyExc_TypeError, "argument must be 'flamingo.sprite.Sprite', not '%s'",
                     spr->ob_type->tp_name);
        return NULL;
    }

    if (PyDict_Contains(self->sprites, (PyObject *)spr) == 0) {
        PyDict_SetItem(self->sprites, spr, PyFloat_FromDouble(0));
        PyDict_SetItem(((Sprite *)spr)->groups, (PyObject *)self, PyFloat_FromDouble(0));
    }

    Py_RETURN_NONE;
}

PyObject *MGroup_add(MGroup *self, PyObject *args) {
    PyObject *result = Group_add((Group *)self, args);
    if (result == NULL)
        return NULL;
    Py_DECREF(result);

    PyObject *spr;
    if (!PyArg_ParseTuple(args, "O", &spr))
        return NULL;

    if (PySequence_Contains(self->sorted, (PyObject *)spr) == 0)
        PyList_Append(self->sorted, spr);
    Py_RETURN_NONE;
}

PyObject *Group_remove(Group *self, PyObject *args) {
    PyObject *spr;
    if (!PyArg_ParseTuple(args, "O", &spr))
        return NULL;

    if (!PyObject_TypeCheck(spr, &Sprite_Type)) {
        PyErr_Format(PyExc_TypeError, "argument must be 'flamingo.sprite.Sprite', not '%s'",
                     spr->ob_type->tp_name);
        return NULL;
    }

    if (PyDict_Contains(self->sprites, (PyObject *)spr) == 1) {
        PyDict_DelItem(self->sprites, spr);
        PyDict_DelItem(((Sprite *)spr)->groups, (PyObject *)self);
    }

    Py_RETURN_NONE;
}

PyObject *MGroup_remove(MGroup *self, PyObject *args) {
    PyObject *result = Group_remove((Group *)self, args);
    if (result == NULL)
        return NULL;
    Py_DECREF(result);

    PyObject *spr;
    if (!PyArg_ParseTuple(args, "O", &spr))
        return NULL;

    if (PySequence_Contains(self->sorted, (PyObject *)spr) == 1)
        PySequence_DelItem(self->sorted, PySequence_Index(self->sorted, spr));
    Py_RETURN_NONE;
}

PyObject *Group_sprites(Group *self) {
    return PyDict_Keys(self->sprites);
}

PyObject *MGroup_sprites(MGroup *self) {
    Py_INCREF(self->sorted);
    return self->sorted;
}

PyObject *MGroup_active_sprites(MGroup *self) {
    if (self->active != NULL) {
        Py_INCREF(self->active);
        return self->active;
    } else {
        return Py_BuildValue("()");
    }
}

PyObject *Group_empty(Group *self) {
    PyObject *items = PyDict_Keys(self->sprites), *current;
    int i, l = PySequence_Length(items);

    for (i = 0; i < l; i++) {
        current = GETITEM(items, i);
        PyDict_DelItem(((Sprite *)current)->groups, (PyObject *)self);
        Py_DECREF(current);
    }

    Py_DECREF(items);
    PyDict_Clear(self->sprites);

    Py_RETURN_NONE;
}

PyObject *MGroup_empty(MGroup *self) {
    PyObject *items = PyDict_Keys(self->sprites), *current;
    int i, l = PySequence_Length(items);

    for (i = 0; i < l; i++) {
        current = GETITEM(items, i);
        PyDict_DelItem(((Sprite *)current)->groups, (PyObject *)self);
        PySequence_DelItem(self->sorted, PySequence_Index(self->sorted, current));
        Py_DECREF(current);
    }

    Py_DECREF(items);
    PyDict_Clear(self->sprites);

    Py_RETURN_NONE;
}

/* Method Declaration */
PyMethodDef Group_methods[] = {
    {"update", (PyCFunction)Group_update, METH_VARARGS, NULL},
    {"add", (PyCFunction)Group_add, METH_VARARGS, NULL},
    {"remove", (PyCFunction)Group_remove, METH_VARARGS, NULL},
    {"sprites", (PyCFunction)Group_sprites, METH_NOARGS, NULL},
    {"empty", (PyCFunction)Group_empty, METH_NOARGS, NULL},
    {NULL}
};

PyMethodDef UGroup_methods[] = {
    {"update", (PyCFunction)UGroup_update, METH_VARARGS, NULL},
    {"add", (PyCFunction)Group_add, METH_VARARGS, NULL},
    {"remove", (PyCFunction)Group_remove, METH_VARARGS, NULL},
    {"sprites", (PyCFunction)Group_sprites, METH_NOARGS, NULL},
    {"empty", (PyCFunction)Group_empty, METH_NOARGS, NULL},
    {NULL}
};

PyMethodDef MGroup_methods[] = {
    {"update", (PyCFunction)MGroup_update, METH_VARARGS, NULL},
    {"add", (PyCFunction)MGroup_add, METH_VARARGS, NULL},
    {"remove", (PyCFunction)MGroup_remove, METH_VARARGS, NULL},
    {"sprites", (PyCFunction)MGroup_sprites, METH_NOARGS, NULL},
    {"active_sprites", (PyCFunction)MGroup_active_sprites, METH_NOARGS, NULL},
    {"empty", (PyCFunction)MGroup_empty, METH_NOARGS, NULL},
    {NULL}
};

/* -------- TYPE DECLARATIONS -------- */
PyTypeObject ObjectState_Type = {
    PyObject_HEAD_INIT(NULL)
    0,                                          /* ob_size */
    "flamingo.sprite.ObjectState",              /* tp_name */
    sizeof(ObjectState),                        /* tp_basicsize */
    0,                                          /* tp_itemsize */
    (destructor)ObjectState_dealloc,            /* tp_dealloc */
    0,                                          /* tp_print */
    0,                                          /* tp_getattr */
    0,                                          /* tp_setattr */
    0,                                          /* tp_compare */
    (reprfunc)ObjectState_repr,                 /* tp_repr */
    0,                                          /* tp_as_number */
    0,                                          /* tp_as_sequence */
    0,                                          /* tp_as_mapping */
    0,                                          /* tp_hash */
    0,                                          /* tp_call */
    (reprfunc)ObjectState_str,                  /* tp_str */
    0,                                          /* tp_getattro */
    0,                                          /* tp_setattro */
    0,                                          /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_CHECKTYPES,   /* tp_flags */
    0,                                          /* tp_doc */
    0,                                      /* tp_traverse */
    0,                                      /* tp_clear */
    0,                                      /* tp_richcompare */
    0,                                      /* tp_weaklistoffset */
    0,                                      /* tp_iter */
    0,                                      /* tp_iternext */
    ObjectState_methods,                        /* tp_methods */
    ObjectState_members,                        /* tp_members */
    0,                                          /* tp_getset */
    0,                                          /* tp_base */
    0,                                          /* tp_dict */
    0,                                          /* tp_descr_get */
    0,                                          /* tp_descr_set */
    0,                                          /* tp_dictoffset */
    (initproc)ObjectState_init,                 /* tp_init */
    0,                                          /* tp_alloc */
    ObjectState_new,                            /* tp_new */
};

PyTypeObject Sprite_Type = {
    PyObject_HEAD_INIT(NULL)
    0,                                          /* ob_size */
    "flamingo.sprite.Sprite",                   /* tp_name */
    sizeof(Sprite),                             /* tp_basicsize */
    0,                                          /* tp_itemsize */
    (destructor)Sprite_dealloc,                 /* tp_dealloc */
    0,                                          /* tp_print */
    0,                                          /* tp_getattr */
    0,                                          /* tp_setattr */
    0,                                          /* tp_compare */
    (reprfunc)Sprite_repr,                      /* tp_repr */
    0,                                          /* tp_as_number */
    0,                                          /* tp_as_sequence */
    &Sprite_MappingMethods,                     /* tp_as_mapping */
    0,                                          /* tp_hash */
    0,                                          /* tp_call */
    (reprfunc)Sprite_str,                       /* tp_str */
    0,                                          /* tp_getattro */
    0,                                          /* tp_setattro */
    0,                                          /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_CHECKTYPES,   /* tp_flags */
    0,                                          /* tp_doc */
    0,                                      /* tp_traverse */
    0,                                      /* tp_clear */
    0,                                      /* tp_richcompare */
    0,                                      /* tp_weaklistoffset */
    0,                                      /* tp_iter */
    0,                                      /* tp_iternext */
    Sprite_methods,                             /* tp_methods */
    Sprite_members,                             /* tp_members */
    Sprite_getsets,                             /* tp_getset */
    0,                                          /* tp_base */
    0,                                          /* tp_dict */
    0,                                          /* tp_descr_get */
    0,                                          /* tp_descr_set */
    0,                                          /* tp_dictoffset */
    (initproc)Sprite_init,                      /* tp_init */
    0,                                          /* tp_alloc */
    Sprite_new,                                 /* tp_new */
};

PyTypeObject Group_Type = {
    PyObject_HEAD_INIT(NULL)
    0,                                          /* ob_size */
    "flamingo.sprite.Group",                    /* tp_name */
    sizeof(Group),                              /* tp_basicsize */
    0,                                          /* tp_itemsize */
    (destructor)Group_dealloc,                  /* tp_dealloc */
    0,                                          /* tp_print */
    0,                                          /* tp_getattr */
    0,                                          /* tp_setattr */
    0,                                          /* tp_compare */
    (reprfunc)Group_repr,                       /* tp_repr */
    0,                                          /* tp_as_number */
    0,                                          /* tp_as_sequence */
    0,                                          /* tp_as_mapping */
    0,                                          /* tp_hash */
    0,                                          /* tp_call */
    (reprfunc)Group_str,                        /* tp_str */
    0,                                          /* tp_getattro */
    0,                                          /* tp_setattro */
    0,                                          /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_CHECKTYPES,   /* tp_flags */
    0,                                          /* tp_doc */
    0,                                      /* tp_traverse */
    0,                                      /* tp_clear */
    0,                                      /* tp_richcompare */
    0,                                      /* tp_weaklistoffset */
    0,                                      /* tp_iter */
    0,                                      /* tp_iternext */
    Group_methods,                              /* tp_methods */
    0,                                          /* tp_members */
    0,                                          /* tp_getset */
    0,                                          /* tp_base */
    0,                                          /* tp_dict */
    0,                                          /* tp_descr_get */
    0,                                          /* tp_descr_set */
    0,                                          /* tp_dictoffset */
    (initproc)Group_init,                       /* tp_init */
    0,                                          /* tp_alloc */
    Group_new,                                  /* tp_new */
};

PyTypeObject UGroup_Type = {
    PyObject_HEAD_INIT(NULL)
    0,                                          /* ob_size */
    "flamingo.sprite._UpdateGroup",             /* tp_name */
    sizeof(Group),                              /* tp_basicsize */
    0,                                          /* tp_itemsize */
    (destructor)Group_dealloc,                  /* tp_dealloc */
    0,                                          /* tp_print */
    0,                                          /* tp_getattr */
    0,                                          /* tp_setattr */
    0,                                          /* tp_compare */
    (reprfunc)UGroup_repr,                      /* tp_repr */
    0,                                          /* tp_as_number */
    0,                                          /* tp_as_sequence */
    0,                                          /* tp_as_mapping */
    0,                                          /* tp_hash */
    0,                                          /* tp_call */
    (reprfunc)UGroup_str,                       /* tp_str */
    0,                                          /* tp_getattro */
    0,                                          /* tp_setattro */
    0,                                          /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_CHECKTYPES,   /* tp_flags */
    0,                                          /* tp_doc */
    0,                                      /* tp_traverse */
    0,                                      /* tp_clear */
    0,                                      /* tp_richcompare */
    0,                                      /* tp_weaklistoffset */
    0,                                      /* tp_iter */
    0,                                      /* tp_iternext */
    UGroup_methods,                             /* tp_methods */
    0,                                          /* tp_members */
    0,                                          /* tp_getset */
    0,                                          /* tp_base */
    0,                                          /* tp_dict */
    0,                                          /* tp_descr_get */
    0,                                          /* tp_descr_set */
    0,                                          /* tp_dictoffset */
    (initproc)MGroup_init,                      /* tp_init */
    0,                                          /* tp_alloc */
    Group_new,                                  /* tp_new */
};

PyTypeObject MGroup_Type = {
    PyObject_HEAD_INIT(NULL)
    0,                                          /* ob_size */
    "flamingo.sprite._MasterGroup",             /* tp_name */
    sizeof(MGroup),                             /* tp_basicsize */
    0,                                          /* tp_itemsize */
    (destructor)MGroup_dealloc,                 /* tp_dealloc */
    0,                                          /* tp_print */
    0,                                          /* tp_getattr */
    0,                                          /* tp_setattr */
    0,                                          /* tp_compare */
    (reprfunc)MGroup_repr,                      /* tp_repr */
    0,                                          /* tp_as_number */
    0,                                          /* tp_as_sequence */
    0,                                          /* tp_as_mapping */
    0,                                          /* tp_hash */
    0,                                          /* tp_call */
    (reprfunc)MGroup_str,                       /* tp_str */
    0,                                          /* tp_getattro */
    0,                                          /* tp_setattro */
    0,                                          /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_CHECKTYPES,   /* tp_flags */
    0,                                          /* tp_doc */
    0,                                      /* tp_traverse */
    0,                                      /* tp_clear */
    0,                                      /* tp_richcompare */
    0,                                      /* tp_weaklistoffset */
    0,                                      /* tp_iter */
    0,                                      /* tp_iternext */
    MGroup_methods,                             /* tp_methods */
    0,                                          /* tp_members */
    0,                                          /* tp_getset */
    &Group_Type,                                /* tp_base */
    0,                                          /* tp_dict */
    0,                                          /* tp_descr_get */
    0,                                          /* tp_descr_set */
    0,                                          /* tp_dictoffset */
    (initproc)MGroup_init,                      /* tp_init */
    0,                                          /* tp_alloc */
    MGroup_new,                                 /* tp_new */
};

/* -------- MODULE INIT -------- */
PyMODINIT_FUNC
initsprite(void)
{
    PyObject *m;

    core_module = ImportModule("flamingo.core");
    if (core_module == NULL)
        Py_Exit(0);

    if (PyType_Ready(&ObjectState_Type) < 0)
        return;
    if (PyType_Ready(&Sprite_Type) < 0)
        return;
    if (PyType_Ready(&Group_Type) < 0)
        return;
    if (PyType_Ready(&UGroup_Type) < 0)
        return;
    if (PyType_Ready(&MGroup_Type) < 0)
        return;

    m = Py_InitModule("sprite", NULL);
    if (m == NULL)
        return;

    Py_INCREF(&ObjectState_Type);
    PyModule_AddObject(m, "ObjectState", (PyObject *)&ObjectState_Type);
    Py_INCREF(&Sprite_Type);
    PyModule_AddObject(m, "Sprite", (PyObject *)&Sprite_Type);
    Py_INCREF(&Group_Type);
    PyModule_AddObject(m, "Group", (PyObject *)&Group_Type);
    Py_INCREF(&UGroup_Type);
    PyModule_AddObject(m, "_UpdateGroup", (PyObject *)&UGroup_Type);
    Py_INCREF(&MGroup_Type);
    PyModule_AddObject(m, "_MasterGroup", (PyObject *)&MGroup_Type);

    Py_DECREF(core_module);
}
